package com.ruoyi.manager;

import com.ruoyi.common.core.domain.entity.system.SysRole;
import com.ruoyi.common.core.domain.entity.system.SysUser;
import com.ruoyi.common.core.domain.entity.wsbean.BaseWsMsgBean;
import com.ruoyi.common.core.domain.entity.wsbean.JishiOrderEndChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.OrderBeginChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.TaocanOrderEndChargeMsg;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.handler.websocket.WebSocketMessageHandler;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocket Channel管理器。
 */
@Slf4j
public class WebSocketChannelManager {
    private static ConcurrentHashMap<String, Channel> tokenAndWebSocketChannelMap = new ConcurrentHashMap<>();
    /**
     * 超级管理员用户id与其WebSocketChannel对应的map集合。
     */
    private static ConcurrentHashMap<Channel, LoginUser> webSocketChannelAndSuperAdminLoginUserMap = new ConcurrentHashMap<>();
    /**
     * 微信小程序用户id与其WebSocketChannel对应的map集合。
     */
    private static ConcurrentHashMap<Channel, LoginUser> webSocketChannelAndMiniprogramLoginUserMap = new ConcurrentHashMap<>();
    /**
     * 景区管理员用户id与其WebSocketChannel对应的map集合。
     */
    private static ConcurrentHashMap<Channel, LoginUser> webSocketChannelAndScenicAdminLoginUserMap = new ConcurrentHashMap<>();
    /**
     * 用于存放服务端向小程序端未发送成功的ws消息，在小程序端重新登录时会将该集合中的消息再次发送给小程序端。
     */
    private static ConcurrentHashMap<Long, List<BaseWsMsgBean>> miniUserIdAndWsMsgBeanMap = new ConcurrentHashMap<>();

    private WebSocketChannelManager() {
    }

    public static void addChannel(String token, LoginUser loginUser, Channel channel) {
        SysUser sysUser = loginUser.getUser();
        if (Objects.nonNull(sysUser)) {
            Long userId = loginUser.getUserId();
            List<SysRole> roles = sysUser.getRoles();
            if (Objects.nonNull(roles) && roles.size() > 0) {
                SysRole sysRole = roles.get(0); //获取角色列表中的第一个角色对象
                Long roleId = sysRole.getRoleId();
                if (roleId == 1) { //超级管理员
                    webSocketChannelAndSuperAdminLoginUserMap.put(channel, loginUser);
                    log.info("超级管理员[userId:{},userName:{},nickName:{},phoneNumber:{}]连接成功，当前已连接的超级管理员数量：{}", userId, loginUser.getUsername(),
                            loginUser.getNickname(), loginUser.getPhonenumber(), webSocketChannelAndSuperAdminLoginUserMap.size());
                } else if (roleId == 3) { //景区管理员
                    webSocketChannelAndScenicAdminLoginUserMap.put(channel, loginUser);
                    log.info("景区管理员[userId:{},userName:{},nickName:{},phoneNumber:{}]连接成功，当前已连接的景区管理员数量：{}", userId, loginUser.getUsername(),
                            loginUser.getNickname(), loginUser.getPhonenumber(), webSocketChannelAndScenicAdminLoginUserMap.size());
                } else if (roleId == 4) { //小程序用户
                    webSocketChannelAndMiniprogramLoginUserMap.put(channel, loginUser);
                    log.info("小程序用户[userId:{},userName:{},nickName:{},phoneNumber:{}]连接成功，当前已连接的小程序用户数量：{}", userId, loginUser.getUsername(),
                            loginUser.getNickname(), loginUser.getPhonenumber(), webSocketChannelAndMiniprogramLoginUserMap.size());

                    //检查当前小程序用户是否存在之前离线期间未收到的ws消息，如果有的话则需重新进行发送
                    List<BaseWsMsgBean> wsMsgBeanList = getWsMsgBeanListByMiniUserId(userId);
                    if (Objects.nonNull(wsMsgBeanList) && wsMsgBeanList.size() > 0) {
                        log.info("小程序用户[userId={}]存在之前离线期间未收到的ws消息总条数: {}，现将全部进行重发!", userId, wsMsgBeanList.size());
                        for (BaseWsMsgBean baseWsMsgBean : wsMsgBeanList) {
                            if (baseWsMsgBean instanceof OrderBeginChargeMsg) { //订单开始计费消息
                                if (wsMsgBeanList.size() == 1) { //当缓存的消息只有一条开始计费的消息时才发送，如果存在多条则不发送，避免同时发送开始计费和停止计费两类消息。
                                    WebSocketMessageHandler.notifyMiniClientOrderBeginCharging(channel, (OrderBeginChargeMsg) baseWsMsgBean);
                                }
                            } else if (baseWsMsgBean instanceof JishiOrderEndChargeMsg) { //停止计时计费订单计费
                                WebSocketMessageHandler.notifyClientSideJishiOrderStopCharging(channel, (JishiOrderEndChargeMsg) baseWsMsgBean);
                            } else if (baseWsMsgBean instanceof TaocanOrderEndChargeMsg) { //停止套餐计费订单计费
                                WebSocketMessageHandler.notifyClientSideTaocanOrderStopCharging(channel, (TaocanOrderEndChargeMsg) baseWsMsgBean);
                            }
                        }

                        //注意：重发结束后续将消息集合清空
                        clearWsMsgBeanListByMiniUserId(userId);
                    } else {
                        log.info("小程序用户[userId={}]不存在之前离线期间未收到的ws消息，无需重发!", userId);
                    }
                }
            }
        }

        tokenAndWebSocketChannelMap.put(token, channel);
        log.info("当前已连接的websocket客户端总数量：{}", tokenAndWebSocketChannelMap.size());
    }

    public static void handleWebsocketChannelInactive(Channel channel) {
        log.info("handleWebsocketChannelInactive channel: {}", channel);
        LoginUser superAdminLoginUser = getSuperAdminLoginUserByWebsocketChannel(channel);
        if (Objects.nonNull(superAdminLoginUser)) {
            //当前断开连接的是超级管理员用户
            webSocketChannelAndSuperAdminLoginUserMap.remove(channel);
            log.info("超级管理员用户[userId:{},userName:{},nickName:{},phoneNumber:{}]的ws断开连接，当前剩余连接的超级管理员的ws客户端数量：{}", superAdminLoginUser.getUserId(),
                    superAdminLoginUser.getUsername(), superAdminLoginUser.getNickname(), superAdminLoginUser.getPhonenumber(), webSocketChannelAndSuperAdminLoginUserMap.size());
        } else {
            LoginUser scenicAdminLoginUser = getScenicAdminLoginUserByWebsocketChannel(channel);
            if (Objects.nonNull(scenicAdminLoginUser)) {
                //当前断开连接的是景区管理员用户
                webSocketChannelAndScenicAdminLoginUserMap.remove(channel);
                log.info("景区管理员用户[userId:{},userName:{},nickName:{},phoneNumber:{}]的ws断开连接，当前剩余连接的景区管理员的ws客户端数量：{}", scenicAdminLoginUser.getUserId(),
                        scenicAdminLoginUser.getUsername(), scenicAdminLoginUser.getNickname(), scenicAdminLoginUser.getPhonenumber(), webSocketChannelAndScenicAdminLoginUserMap.size());
            } else {
                LoginUser miniprogramLoginUser = getMiniprogramLoginUserByWebsocketChannel(channel);
                if (Objects.nonNull(miniprogramLoginUser)) {
                    //当前断开连接的是小程序用户
                    webSocketChannelAndMiniprogramLoginUserMap.remove(channel);
                    log.info("小程序用户[userId:{},userName:{},nickName:{},phoneNumber:{}]的ws断开连接，当前剩余连接的小程序用户的ws客户端数量：{}", miniprogramLoginUser.getUserId(),
                            miniprogramLoginUser.getUsername(), miniprogramLoginUser.getNickname(), miniprogramLoginUser.getPhonenumber(), webSocketChannelAndMiniprogramLoginUserMap.size());
                }
            }
        }

        removeChannel(channel);
    }

    public static void removeChannel(Channel channel) {
        String targetToken = "";
        for (String token : tokenAndWebSocketChannelMap.keySet()) {
            if (channel.equals(tokenAndWebSocketChannelMap.get(token))) {
                targetToken = token;
                break;
            }
        }
        if (StringUtils.hasText(targetToken)) {
            tokenAndWebSocketChannelMap.remove(targetToken);
            log.info("当前剩余连接的websocket客户端总数量：{}", tokenAndWebSocketChannelMap.size());
        }
    }

    /**
     * 从boatIdChannelMap中移除所有Channel。
     */
    public static void removeAllChannel() {
        tokenAndWebSocketChannelMap.clear();
        webSocketChannelAndMiniprogramLoginUserMap.clear();
    }

    /**
     * 通过用户token获取其关联的channel。
     *
     * @param token
     * @return
     */
    public static Channel getChannelByToken(String token) {
        if (!StringUtils.hasText(token)) return null;
        return tokenAndWebSocketChannelMap.get(token);
    }

    /**
     * 通过userId获取其关联的小程序用户的Websocket Channel。
     *
     * @param userId
     * @return
     */
    public static Channel getMiniprogramWebsocketChannelByUserId(Long userId) {
        if (Objects.isNull(userId)) return null;
        for (Channel channel : webSocketChannelAndMiniprogramLoginUserMap.keySet()) {
            LoginUser miniprogramLoginUser = webSocketChannelAndMiniprogramLoginUserMap.get(channel);
            if (Objects.nonNull(miniprogramLoginUser) && Objects.equals(miniprogramLoginUser.getUserId(), userId)) {
                return channel;
            }
        }
        return null;
    }

    public static LoginUser getMiniprogramLoginUserByWebsocketChannel(Channel channel) {
        return webSocketChannelAndMiniprogramLoginUserMap.get(channel);
    }

    /**
     * 通过scenicId获取其关联的channel。
     *
     * @param scenicId
     * @return
     */
    public static Channel getScenicWebSocketChannelByScenicId(Long scenicId) {
        if (Objects.isNull(scenicId)) return null;
        for (Channel channel : webSocketChannelAndScenicAdminLoginUserMap.keySet()) {
            LoginUser scenicLoginUser = webSocketChannelAndScenicAdminLoginUserMap.get(channel);
            if (Objects.nonNull(scenicLoginUser) && Objects.equals(scenicLoginUser.getScenicId(), scenicId)) {
                return channel;
            }
        }
        return null;
    }

    /**
     * 通过scenicId获取其关联的channel集合(因为有可能同一个景区管理员账号在多个终端上同时登录)。
     *
     * @param scenicId
     * @return
     */
    public static List<Channel> getScenicWebSocketChannelListByScenicId(Long scenicId) {
        if (Objects.isNull(scenicId)) return null;
        List<Channel> channelList = new ArrayList<>();
        for (Channel channel : webSocketChannelAndScenicAdminLoginUserMap.keySet()) {
            LoginUser scenicLoginUser = webSocketChannelAndScenicAdminLoginUserMap.get(channel);
            if (Objects.nonNull(scenicLoginUser) && Objects.equals(scenicLoginUser.getScenicId(), scenicId)) {
                channelList.add(channel);
            }
        }
        return channelList;
    }

    public static LoginUser getScenicAdminLoginUserByWebsocketChannel(Channel channel) {
        return webSocketChannelAndScenicAdminLoginUserMap.get(channel);
    }

    /**
     * 根据channel获取超级管理员登录用户对象。
     *
     * @param channel
     * @return
     */
    public static LoginUser getSuperAdminLoginUserByWebsocketChannel(Channel channel) {
        return webSocketChannelAndSuperAdminLoginUserMap.get(channel);
    }

    /**
     * 根据channel获取对应的用户token。
     *
     * @param
     * @return
     * @author ZJW
     * @createTime 2023/7/14 23:18
     */
    public static String getTokenByWebsocketChannel(Channel channel) {
        for (String key : tokenAndWebSocketChannelMap.keySet()) {
            log.info("getTokenByChannel tokenAndWebSocketChannelMap: {} --- {}", key, tokenAndWebSocketChannelMap.get(key));
        }

        for (String token : tokenAndWebSocketChannelMap.keySet()) {
            if (tokenAndWebSocketChannelMap.get(token).equals(channel)) {
                return token;
            }
        }
        return null;
    }

    /**
     * 检查及打印当前WebSocket连接情况。
     */
    public static void printCurrentWebsocketConnectionStatus() {
        log.info("当前ws连接总数：{}, 超级管理员用户ws连接数：{},小程序用户ws连接数：{}, 景区管理员ws连接数：{}", tokenAndWebSocketChannelMap.size(), webSocketChannelAndSuperAdminLoginUserMap.size(),
                webSocketChannelAndMiniprogramLoginUserMap.size(), webSocketChannelAndScenicAdminLoginUserMap.size());
        log.info("超级管理员用户ws连接情况如下：");
        LoginUser superAdminLoginUser;
        for (Channel webSocketChanne : webSocketChannelAndSuperAdminLoginUserMap.keySet()) {
            superAdminLoginUser = webSocketChannelAndSuperAdminLoginUserMap.get(webSocketChanne);
            log.info("{} =========> [userId:{},userName:{},nickName:{},phoneNumber:{}]", webSocketChanne, superAdminLoginUser.getUserId(), superAdminLoginUser.getUsername(),
                    superAdminLoginUser.getNickname(), superAdminLoginUser.getPhonenumber());
        }
        log.info("==============分割线=====================");
        log.info("小程序用户ws连接情况如下：");
        LoginUser miniprogramLoginUser;
        for (Channel webSocketChanne : webSocketChannelAndMiniprogramLoginUserMap.keySet()) {
            miniprogramLoginUser = webSocketChannelAndMiniprogramLoginUserMap.get(webSocketChanne);
            log.info("{} =========> [userId:{},userName:{},nickName:{},phoneNumber:{}]", webSocketChanne, miniprogramLoginUser.getUserId(), miniprogramLoginUser.getUsername(),
                    miniprogramLoginUser.getNickname(), miniprogramLoginUser.getPhonenumber());
        }
        log.info("==============分割线=====================");
        log.info("景区管理员ws连接情况如下：");
        LoginUser scenicLoginUser;
        for (Channel webSocketChanne : webSocketChannelAndScenicAdminLoginUserMap.keySet()) {
            scenicLoginUser = webSocketChannelAndScenicAdminLoginUserMap.get(webSocketChanne);
            log.info("{} =========> [userId:{},userName:{},nickName:{},phoneNumber:{}]", webSocketChanne, scenicLoginUser.getUserId(), scenicLoginUser.getUsername(),
                    scenicLoginUser.getNickname(), scenicLoginUser.getPhonenumber());
        }
    }

    public static void addWsMsgBean(Long userId, BaseWsMsgBean wsMsgBean) {
        List<BaseWsMsgBean> wsMsgBeanList = miniUserIdAndWsMsgBeanMap.get(userId);
        if (Objects.nonNull(wsMsgBeanList)) {
            wsMsgBeanList.add(wsMsgBean);
        } else {
            wsMsgBeanList = new ArrayList<>();
            wsMsgBeanList.add(wsMsgBean);
            miniUserIdAndWsMsgBeanMap.put(userId, wsMsgBeanList);
        }
    }

    public static List<BaseWsMsgBean> getWsMsgBeanListByMiniUserId(Long userId) {
        return miniUserIdAndWsMsgBeanMap.get(userId);
    }

    public static void clearWsMsgBeanListByMiniUserId(Long userId) {
        List<BaseWsMsgBean> wsMsgBeanList = miniUserIdAndWsMsgBeanMap.get(userId);
        if (Objects.nonNull(wsMsgBeanList) && wsMsgBeanList.size() > 0) {
            wsMsgBeanList.clear();
        }
    }

}
